using System.Collections.Generic;
using System.CodeDom.Compiler;
using System.Reflection;

namespace NScriptEngine
{
    internal class PyScript : Script
    {
        static public bool IsValidFileExtension(string fileExt)
        {
            if (fileExt == "ipy" || fileExt == "py")
                return true;
            return false;
        }

        public PyScript(string pyFile, Dictionary<string, object> references)
        {
            _python = new IronPython.Hosting.PythonEngine();
            _python.AddToPath(System.Environment.CurrentDirectory);

            foreach (string name in references.Keys)
            {
                AddReference(name, references[name]);
            }

            _python.ExecuteFile(pyFile);
        }

        public override void CastEvent(string eventName, object[] eventArgs)
        {
            // Has OnEvent method?
            if (!_python.Globals.ContainsKey("OnEvent"))
            {
                return;
            }

            // stuff args into global space
            _python.Execute("lEventArgs = []\n");
            foreach (object eventArg in eventArgs)
            {
                _python.Globals["arg"] = eventArg;
                _python.Execute("lEventArgs.append(arg)\n");
            }

            // Call OnEvent.
            _python.Execute(string.Format("OnEvent(\"{0}\",lEventArgs)\n", eventName));
        }

        public override void AddReference(string name, object obj)
        {
            _python.Globals.Add(name, obj);
        }

        private IronPython.Hosting.PythonEngine _python;
    }

    /////////////////////////////////////////////////////////
    /////////////////////////////////////////////////////////

    internal abstract class MSScript : Script
    {
        public override void CastEvent(string eventName, object[] eventArgs)
        {
            object[] args = new object[2] { eventName, eventArgs };
            foreach (CodeHandle codeHandle in _codeHandles)
            {
                codeHandle.InvokeMethod("CastEvent", args);
            }
        }

        public override void AddReference(string name, object obj)
        {
        }

        public MSScript(string msFile, Dictionary<string, object> references)
        {
            CompilerParameters compilerOps = _GetOps();
            CodeDomProvider codeProvider = GetCodeCompiler();

            _compilerResults = codeProvider.CompileAssemblyFromFile(compilerOps, msFile);
            _codeHandles = new List<CodeHandle>();

            // instantiate all the objects from the script
            foreach (System.Type t in _compilerResults.CompiledAssembly.GetTypes())
            {
                CodeHandle codeHandle = GetCodeHandle(t);
                if (null != codeHandle)
                {
                    _codeHandles.Add(codeHandle);
                }
            }  
        }

        private System.Collections.Generic.List<CodeHandle> _codeHandles;
        private CompilerParameters _GetOps()
        {
            CompilerParameters compilerOps = new CompilerParameters();
            
            compilerOps.GenerateInMemory = true;
            compilerOps.GenerateExecutable = false;
            compilerOps.IncludeDebugInformation = false;

            return compilerOps;
        }
        protected abstract CodeDomProvider GetCodeCompiler();
        protected CompilerResults _compilerResults;

        protected CodeHandle GetCodeHandle(System.Type t)
        {
            // validate the object
            //...

            // Stuff it into a code handle
            return new CodeHandle(t);
        }

        protected class CodeHandle
        {
            private object _object;
            private System.Type _type;

            public object Object
            {
                get
                {
                    return _object;
                }
            }
            public System.Type Type
            {
                get
                {
                    return _type;
                }
            }
            
            public CodeHandle(System.Type t)
            {
                _type = t;
                _object = System.Activator.CreateInstance(_type);
            }

            public object InvokeMethod(string methodName, object[] args)
            {
                // Deduce the 'Type' for each element in the arg list.
                System.Type[] argTypes = new System.Type[args.Length];
                for( int i=0 ; i<args.Length ; ++i )
                {
                    argTypes[i] = args[i].GetType();
                }

                // Pull the method out of this object's 'Type'.
                MethodInfo method = Type.GetMethod(methodName, argTypes);
                if (null == method)
                {
                    return null;
                }

                // Go, go gadget method!
                return method.Invoke(Object, args);
            }
        }
    }

    internal class CSharpScript : MSScript
    {
        static public bool IsValidFileExtension(string fileExt)
        {
            if (fileExt == "cs")
                return true;
            return false;
        }

        public CSharpScript(string csFile, Dictionary<string, object> references)
            : base(csFile, references)
        {
        }

        protected override CodeDomProvider GetCodeCompiler()
        {
            return new Microsoft.CSharp.CSharpCodeProvider();
        }
    }

    internal class VBScript : MSScript
    {
        static public bool IsValidFileExtension(string fileExt)
        {
            if (fileExt == "vb")
                return true;
            return false;
        }

        public VBScript(string vbFile, Dictionary<string, object> references)
            : base(vbFile, references)
        {
        }

        protected override CodeDomProvider GetCodeCompiler()
        {
            return new Microsoft.VisualBasic.VBCodeProvider();
        }
    }
}